package com.chen.spikemall.redis;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;

import java.util.UUID;

public class RedisLock {

    private String REDIS_LOCK_PREX = "redis_lock_";

    private StringRedisTemplate redisTemplate;

    private static final Long TTL = 10000L;

    private String threadId = UUID.randomUUID().toString();

    private static final String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return -1 end";
    
    public RedisLock(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * Redis分布式锁，尝试上锁
     *
     * @return 1 代表上锁成功，-1代表失败
     * @param key 上锁的关键字
     *
     * */
    public int tryLock(String key) {

        final String lockKey = REDIS_LOCK_PREX + key;
        Boolean ret = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {

                Boolean result = connection.set(lockKey.getBytes(), threadId.getBytes(), Expiration.milliseconds(TTL), RedisStringCommands.SetOption.SET_IF_ABSENT);
                return result;
            }
        });

        return ret != null && ret ? 1 : -1;
    }



    /**
     * Redis分布式锁，上锁
     * 如果上锁失败阻塞，知道等待锁释放唤醒并重新获取锁
     *
     * @param key 上锁的关键字
     *
     * */
    public void Lock(String key) {

        final String lockKey = REDIS_LOCK_PREX + key;

        while (true) {
            Boolean ret = redisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {

                    Boolean result = connection.set(lockKey.getBytes(), threadId.getBytes(), Expiration.milliseconds(TTL), RedisStringCommands.SetOption.SET_IF_ABSENT);
                    return result;
                }
            });

            if (ret != null && ret) {

            }
        }
    }



    /**
     * Redis分布式锁，解锁
     *
     * @return 1 代表成功，-1代表失败
     * @param key 上锁的关键字
     *
     * */
    public int unlock(String key) {

        Long ret = redisTemplate.execute(new RedisCallback<Long>() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {

                Object ret = connection.eval(luaScript.getBytes(), ReturnType.INTEGER, 1, (REDIS_LOCK_PREX + key).getBytes(), threadId.getBytes());
                Long result = (Long) ret;
                return result;
            }
        });
        return ret != null ? ret.intValue() : -1;
    }
}
